﻿@*
    For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
*@

@{
    ViewData["Title"] = "Job";
}

<link rel="stylesheet" href="~/layui/css/layui.css" media="all">

<script>
    var cluster = "@ViewData["cluster"]";
    var elasticsearchHost = 'http://@Context.Request.Host:9200';
    var logPages;
    var logNum;

    function getJobLog(payload) {
        var jobId = '@ViewData["jobid"]'
        var client = new elasticsearch.Client({
            host: elasticsearchHost,
            //log: 'trace'
        });

        var searchParams = {
            index: '',
            _source: "log",
            body: {
                query: {
                    filtered: {
                        query: {
                            match: {
                                "kubernetes.pod_name": jobId,
                            },
                        }

                    }
                }
            }
        };



        client.count(searchParams, function (err, res) {
            logNum = res.count;
            layui.use(['laypage', 'layer'], function () {
                var laypage = layui.laypage
                    , layer = layui.layer;

                laypage.render({
                    elem: 'logautopager'
                    , count: logNum
                    , theme: '#1E9FFF'
                    , layout: ['prev', 'page', 'next']
                    , curr: (logNum / 500) + 1
                    , prev: 'Prev'
                    , next: 'Next'
                    , limit: 500
                    , limits: [500, 1000, 1500, 2000, 5000]
                    , jump: function (obj, first) {

                        //console.log(obj);
                        if (obj.curr != parseInt((logNum / 500) + 1)) {
                            layer.msg('Loaded Job Log Page: ' + obj.curr);
                            document.getElementById('chkRefresh').checked = false;
                        }
                        if (logNum > 0) {
                            var searchParams = {
                                index: '',
                                from: (obj.curr - 1) * obj.limit,
                                size: obj.limit,
                                sort: "time:asc",
                                _source: "log,time",
                                body: {
                                    query: {
                                        filtered: {
                                            query: {
                                                match: {
                                                    "kubernetes.pod_name": jobId,
                                                },
                                            }

                                        }
                                    }
                                }
                            };

                            client.search(searchParams, function (err, res) {
                                var logstr = "";
                                var hits = res.hits.hits.sort(function (a, b) {
                                    if (a._source.time < b._source.time)
                                        return -1;
                                    else
                                        return 1;

                                })
                                for (var i = 0; i < hits.length; i++) {
                                    if (document.getElementById('chkTimestamp').checked) {
                                        logstr += '[' + hits[i]._source.time + '] ' + hits[i]._source.log;
                                    }
                                    else
                                    {
                                        logstr += hits[i]._source.log;
                                    }
                                }

                                var objDiv = document.getElementById('logPanel');
                                objDiv.style.height = "500px";
                                objDiv.innerHTML = logstr;
                                if (obj.curr == parseInt((logNum / 500) + 1)) {
                                    objDiv.scrollTop = objDiv.scrollHeight;
                                }
                                else {
                                    objDiv.scrollTop = 0;
                                }
                            });
                        }
                        else
                        {
                            if (payload.log) {
                                //console.log(payload.jobId);
                                if (payload.log != "fail-to-get-logs") {
                                    var objDiv = document.getElementById('logPanel');
                                    objDiv.style.height = "500px";
                                    objDiv.innerHTML = payload.log;
                                    objDiv.scrollTop = objDiv.scrollHeight;
                                }
                            }
                            else {
                                var logPanelDiv = document.getElementById('logPanel');
                                logPanelDiv.innerHTML = "No log yet!";
                                logPanelDiv.style.height = "50px";
                            }

                        }
                    }
                });
            });
        });
    }


    function getCommands() {
        $.ajax({ url: "/api/dlws/GetCommands?cluster=" + cluster + "&jobId=@ViewData["jobid"]", dataType: 'json', timeout: 10000, error: AlertAPIError }).done(
            function (json) {
                var commandsDiv = document.getElementById('commands-div');
                if (json.length == 0) {
                    commandsDiv.style.display = "none";
                }
                else {
                    commandsDiv.style.display = "block";
                    var table = $('#command-table');
                    table.find("tr").remove();
                    for (var i = 0; i < json.length; i++) {
                        var tr
                        tr = $('<tr/>');
                        //Time
                        tr.append("<td>" + json[i].time + "</td>");
                        //Command
                        tr.append("<td>" + json[i].command + "</td>");
                        //Status
                        if (json[i].status === "run") {
                            var msg = (json[i].output || 'null').toString().replace(/\n/g, "<br/>");
                            var title = json[i].command.toString().replace(/\n/g, " ");
                            var resultButton = '<a href="#" onclick="layer.alert(\'' + msg + '\',{ btn: \'Ok\', title: \'' + title + '\'}); return false;">Display Result</a>';
                            tr.append('<td><bold class="Admin">' + json[i].status + "</bold> | " + resultButton + '</td>');
                        }
                        else {
                            tr.append('<td><bold class="Alias">' + json[i].status + '</bold></td>');
                        }

                        table.append(tr);
                    }
                }
            });
    }

    function sortSSH(a, b) {
        var worker1 = a.podName.split("-").pop().replace("worker", "");
        var worker2 = b.podName.split("-").pop().replace("worker", "");
        return parseInt(worker1) - parseInt(worker2);
    }

    function getEndpoints() {
        $.ajax({
            url: "/api/dlws/GetEndpoints?cluster=@ViewData["cluster"]&jobId=@ViewData["jobid"]", dataType: 'json'
        }).then(function (endpoints) {
            $('#endpoints').show();

            var order = { ssh: 1, ipython: 2, tensorboard: 3 };

            endpoints = endpoints.filter(function (endpoint) {
                return endpoint.podName.indexOf("ps0") === -1
            });

            endpoints.sort(function (a, b) {
                var orderA = order[a.name] || Infinity;
                var orderB = order[b.name] || Infinity;

                if (orderA !== orderB) { return orderA - orderB; }

                if (isFinite(orderA)) {     
                    if (orderA === order["ssh"]) {
                        return sortSSH(a, b);
                    } else {
                        return a.nodeName.localeCompare(b.nodeName);
                    }
                } else {
                    return a.podPort - b.podPort;
                }
            });

            var endpointsArr = $.map(endpoints, function (endpoint) {
                var task = "";

                if (endpoint.podName) {
                    task = endpoint.podName.split("-").pop();
                }

                if (task.indexOf("ps0") !== -1 && endpoint.name.indexOf("ssh") !== -1) {
                    return "";
                }
                var taskInfo = task.indexOf("worker") === -1 ? "" : " " + task;
                if (endpoint.name == "ssh") {
                    window.ngScope.$applyAsync('ssh = true');
                    if (endpoint.status == "running") {
                        var workPath = "@ViewData["workPath"]";
                        var sessionUserName = "@ViewData["Username"]";
                        var password = "@ViewData["password"]";
                        var jobUserName = endpoint.username;
                        workPath = workPath.replace(sessionUserName, jobUserName);
                        return taskInfo + " SSH: <span onclick='copy(this)'>ssh -i " + workPath + ".ssh/id_rsa -p " + endpoint.port + " " + jobUserName + "@@" + endpoint.nodeName + "." + endpoint.domain + "(password:"+ password + ")</span>" + "\n";
                    } else {
                        return;
                    }
                }
                else {
                    if (endpoint.name === "ipython") {
                        window.ngScope.$applyAsync('ipython = true');
                    } else if (endpoint.name === "tensorboard") {
                        window.ngScope.$applyAsync('tensorboard = true');
                    }
                    if (endpoint.status == "running") {
                        var svcurl = "http://" + endpoint.nodeName + "." + endpoint.domain + ":" + endpoint.port + "/";
                        return " " + endpoint.name + ": <a href='" + svcurl + "' target='_blank'>" + svcurl + "</a>\n";
                    } else {
                        return;
                    }
                }
            });
            var sshContent = "";
            var ipythonContent = "";
            var tensorboardContent = "";
            var otherContent = "";
            var endPointName = "";
            for (var i = 0; i < endpointsArr.length; i++) {
                if (endpointsArr[i] === "") {
                    continue;
                } else if (endpointsArr[i].indexOf("ssh") !== -1) {
                    sshContent += endpointsArr[i];
                } else if (endpointsArr[i].indexOf("ipython") !== -1) {
                    ipythonContent += endpointsArr[i];
                } else if (endpointsArr[i].indexOf("tensorboard") !== -1) {
                    tensorboardContent += endpointsArr[i];
                } else {
                     endPointName = endpointsArr[i].split(" ")[0];
                     otherContent += endpointsArr[i];
                }
            }

            otherContent = endPointName + otherContent;
            var endpointsStr = sshContent + "<div style='height:10px;'></div>" + ipythonContent + "<div style='height:10px;'></div>" + tensorboardContent + "<div style='height:10px;'></div>" + otherContent;

            var endpointsPanellDiv = document.getElementById('endpointsPanel');
            if (endpointsPanellDiv.innerHTML != endpointsStr) {
                endpointsPanellDiv.innerHTML = endpointsStr;
            }
        })
    }

    function buildTemplateList() {
        $.ajax({ url: "/api/dlws/GetTemplates?cluster=" + cluster + "&type=command", dataType: 'json', timeout: 10000, error: AlertAPIError }).done(
            function (json) {
                $('#templateselect').find("option").remove();
                for (var i = 0; i < json.length; i++) {
                    var row = $("<option>" + json[i].Name + "</option>");
                    row.attr("value", JSON.stringify(json[i].Json));
                    $('#templateselect').append(row);
                }
            });
    }

    function DisplayDetail(payload) {
        if (payload.jobParams) {

            var jobDetailDiv = document.getElementById('jobDetail');
            var jobDetailStr = "Job Id: " + payload.jobParams.jobId + "\n";
            jobDetailStr += "Job Name: " + payload.jobParams.jobName + "\n";
            jobDetailStr += "Docker Image: " + payload.jobParams.image + "\n";
            jobDetailStr += "Command: " + payload.jobParams.cmd + "\n";
            jobDetailStr += "Data Path: " + payload.jobParams.dataPath + "\n";
            jobDetailStr += "Work Path: " + payload.jobParams.workPath + "\n";
            jobDetailStr += "Job Path: " + payload.jobParams.jobPath + "\n";
            jobDetailStr += "Job Type: " + payload.jobParams.jobType + "\n";
            jobDetailStr += "GPU # Per Worker: " + payload.jobParams.resourcegpu + "\n";
            jobDetailStr += "Job Status: " + payload.jobStatus + "\n";
            jobDetailStr += "Job Submission Time: " + payload.jobTime + "\n";
            if (jobDetailDiv.innerHTML != jobDetailStr) {
                jobDetailDiv.innerHTML = jobDetailStr;
            }
            var jobfolderDiv = document.getElementById('jobfolderPanel');
            var jobfolderStr = "<a href='file:@ViewData["jobPath"]" + payload.jobParams.jobPath + "'> file:@ViewData["jobPath"]" + payload.jobParams.jobPath + "</a>";
            if (jobfolderDiv.innerHTML != jobfolderStr) {
                jobfolderDiv.innerHTML = jobfolderStr;
            }

            document.getElementById('run-command').style.display = payload.jobStatus == "running" ? "block" : "none";
            window.ngScope.$applyAsync(function (scope) { scope.status = payload.jobStatus });
            if (payload.jobStatus == "running") {
                getCommands();
                getEndpoints();
            }
        }



        var client = new elasticsearch.Client({
            host: elasticsearchHost,
            log: [{
                levels: ["error"] // change these options
            }]
        });

        client.ping({
            requestTimeout: 1000,
        }, function (error) {
            if (error) {
                if (payload.log) {
                    //console.log(payload.jobId);
                    if (payload.log != "fail-to-get-logs") {
                        var objDiv = document.getElementById('logPanel');
                        objDiv.style.height = "500px";
                        objDiv.innerHTML = payload.log;
                        objDiv.scrollTop = objDiv.scrollHeight;
                    }
                }
                else {
                    var logPanelDiv = document.getElementById('logPanel');
                    logPanelDiv.innerHTML = "No log yet!";
                    logPanelDiv.style.height = "50px";
                }
            } else {
                getJobLog(payload);
            }
            });

        if ($('#elasticsearchlog').is(":visible"))
        {
        }

    }

    function GetDetail() {
        var url = "/api/dlws/JobDetail?cluster=" + cluster + "&jobId=@ViewData["jobid"]";
        $.ajax({ url: url, dataType: 'json', timeout: 15000 }).done(DisplayDetail);
    }

    function RefreshDetail() {
        if (document.getElementById('chkRefresh').checked) {
            GetDetail();
        }
    }

    $(document).ready(function () {
        GetDetail();
        buildTemplateList();
        setInterval(RefreshDetail, 15000);
        $(function () {
            $('[data-toggle="tooltip"]').tooltip({
                container: 'body',
                show: {
                    effect: "slideDown",
                    delay: 1000
                }
            })
        })
    });

    function copy(self) {
        var input = document.createElement("input");
        document.body.appendChild(input);
        input.setAttribute("value", self.innerHTML);
        input.select();
        if (document.execCommand('copy')) {
            document.execCommand('copy')
            $('.alert').html('copied!').addClass('alert-message').show().delay(1500).fadeOut();
        }
        document.body.removeChild(input);
    }


    function copyIconShow(self) {
        self.style.background = "#b3b3b3";
         $('.alert').html('copy to clipboard').addClass('alert-message').show();
    }

    function copyIconHidden(self) {
        self.style.background = "";
        $('.alert-message').delay(1000).fadeOut();
    }

</script>
<script>
    function AlertCommandResult(payload) {
        layer.alert(payload.result, { btn: 'Ok', title: 'alert' });
        $("#run-cmd-btn").attr("disabled", false);
        document.getElementById("cmd-textbox").value = '';
    }

    function AlertAPIError(xhr, ajaxOptions, thrownError) {
        //alert("Restful API Error: \n" + JSON.stringify(thrownError));
        console.log("Restful API Error: \n" + JSON.stringify(thrownError));
        $("#run-cmd-btn").attr("disabled", false);
    }

    function RunCommand() {
        $("#run-cmd-btn").attr("disabled", true);
        var url = "/api/dlws/RunCommand?cluster=" + cluster + "&jobId=@ViewData["jobid"]&";
        url = url + "command=" + encodeURIComponent(document.getElementById("cmd-textbox").value);
        $.ajax({ url: url, dataType: 'json', timeout: 10000,  error: AlertAPIError }).done(AlertCommandResult);
    }

    function UseTemplate(tempValue) {
        var temp = JSON.parse(tempValue);
        var cmdString = (temp.cmd) ? temp.cmd : "";
        document.getElementById("cmd-textbox").value = cmdString;
    }

</script>
<script>
    void function (app) {
        app.controller("endpoints", function ($scope, $http) {
            $scope.$watch("ssh", function (value, oldValue) {
                if (value === oldValue) { return; }
                if (value) {
                    $http({
                        method: "POST",
                        url: "/api/dlws/endpoints",
                        params: { cluster: cluster },
                        data: {
                            jobId: '@ViewData["jobid"]',
                            endpoints: ["ssh"]
                        }
                    }).catch(function (response) {
                        var message = "RestfulAPI error!";
                        if (response.status === 400) {
                            message = response.data;
                        }
                        layer.alert(message, { icon: 5, btn: 'Ok', title: 'alert' });
                        $scope.ssh = false;
                    });
                }
            })
            $scope.$watch("ipython", function (value, oldValue) {
                if (value === oldValue) { return; }
                if (value) {
                    $http({
                        method: "POST",
                        url: "/api/dlws/endpoints",
                        params: { cluster: cluster },
                        data: {
                            jobId: '@ViewData["jobid"]',
                            endpoints: ["ipython"]
                        }
                    }).catch(function (response) {
                        var message = "RestfulAPI error!";
                        if (response.status === 400) {
                            message = response.data;
                        }
                        layer.alert(message, { icon: 5, btn: 'Ok', title: 'alert' });
                        $scope.ipython = false;
                    });
                }
            })
            $scope.$watch("tensorboard", function (value, oldValue) {
                if (value === oldValue) { return; }
                if (value) {
                    $http({
                        method: "POST",
                        url: "/api/dlws/endpoints",
                        params: { cluster: cluster },
                        data: {
                            jobId: '@ViewData["jobid"]',
                            endpoints: ["tensorboard"]
                        }
                    }).catch(function (response) {
                        var message = "RestfulAPI error!";
                        if (response.status === 400) {
                            message = response.data;
                        }
                        layer.alert(message, { icon: 5, btn: 'Ok', title: 'alert' });
                        $scope.tensorboard = false;
                    });
                }
            })
            $scope.addPort = function () {
                $scope.addPortForm.$setSubmitted();
                $http({
                    method: "POST",
                    url: "/api/dlws/endpoints",
                    params: { cluster: cluster },
                    data: {
                        jobId: '@ViewData["jobid"]',
                        endpoints: [{ name: 'port-' + $scope.port, podPort: $scope.port }]
                    }
                }).then(function (response) {
                    if (typeof response.data === 'object') {
                        delete $scope.port;
                        $scope.addPortForm.$setPristine();
                        $scope.addPortForm.$setUntouched();
                    } else {
                        layer.alert("Restful API Error: \n" + response.data, { icon: 5, btn: 'Ok', title: 'alert' });
                    }
                }, function (response) {
                    var message = "RestfulAPI error!";
                    if (response.status === 400) {
                        message = response.data;
                    }
                    layer.alert(message, { icon: 5, btn: 'Ok', title: 'alert' });
                }).finally(function () {
                    $scope.addPortForm.$submitted = false;
                });
            }

            window.ngScope = $scope;
        });
    }(angular.module("endpoints", ["ngMessages", "ngMaterial"]));
</script>

<h2> Job Details:</h2>
<div id="jobDetail" style="height: 230px;" readonly="" class="ng-binding grey-box scroll-box">
</div>
<h2 style="margin-top:20px;"> Job Folder: </h2>
<div id="jobfolderPanel" style="height: 20px;" readonly="" class="ng-binding grey-box">
</div>
<div id="endpoints" style="margin-top:20px;" hidden>
    <h2> Mapped Endpoints: </h2>
    <h4>(Links to access interactive/visualization interface)</h4>
    <div id="endpointsPanel" readonly="" class="ng-binding grey-box">
    </div>
    <div class="alert"></div>
</div>
<!-- introduce angular.js just for md-switch in AngularJS Material -->
<div ng-app="endpoints" ng-controller="endpoints">
    <div layout="row" layout-align="space-between">
        <md-switch ng-model="ssh" ng-disabled="status !== 'running' || ssh" flex="60">SSH</md-switch>
        <md-switch ng-model="ipython" ng-disabled="status !== 'running' || ipython" flex="60">iPython</md-switch>
        <md-switch ng-model="tensorboard" ng-disabled="status !== 'running' || tensorboard" flex="60">
            Tensorboard
            <span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
        </md-switch>
    </div>
    <div layout="row" layout-align="space-between">
        <p style="font-size: small">
            <span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
            Tensorboard will listen on directory <samp>~/tensorboard/${DLWS_JOB_ID}/logs</samp> inside docker container.
        </p>
    </div>
    <form name="addPortForm" layout="row" ng-submit="addPort()" style="margin-top: 20px">
        <md-input-container flex>
            <label>New Interactive Port (40000-49999)</label>
            <input name="port"
                   type="number" min="40000" max="49999"
                   ng-model="port"
                   ng-disabled="status !== 'running' || addPortForm.$submitted" ng-required="true" />
            <div ng-messages="addPortForm.port.$error">
                <div ng-message="required">Port is required.</div>
                <div ng-message="min">Port should be &gt;= 40000</div>
                <div ng-message="max">Port should be &lt;= 49999</div>
            </div>
        </md-input-container>
        <md-button type="submit" class="md-primary" ng-disabled="status !== 'running' || addPortForm.$submitted || addPortForm.$invalid">Add</md-button>
    </form>
</div>
<h2> Job Console Output:</h2>
<div id="logPanel" style="height: 500px;" readonly="" class="ng-binding grey-box scroll-box">
</div>
<div id="logautopager" style="float:right"></div>

<div>
    <input type="checkbox" id="chkRefresh" checked="checked" onclick="RefreshDetail()" />
    <label>
        Auto-Refresh Job Logs (Refresh-Rate 5 Seconds)
    </label>
    <input type="checkbox" id="chkTimestamp" checked="checked" onclick="RefreshDetail()" />
    <label>
        Show timestamp in job logs
    </label>
</div>

<div id="run-command" style="display: none">
    <h2 style="margin-top:20px;"> Run Command:</h2>
    <label>Use Template: </label>
    <select onchange="UseTemplate(this.value)" id="templateselect"></select>
    <div style="display: block; position: relative; width: 100%;">
        <textarea class="form-control cmd-textbox" style="height: 10em; display: block" id="cmd-textbox"></textarea>
        <button class="btn btn-success" style="position: absolute; bottom: 10px; right: 10px" onclick="RunCommand()" id="run-cmd-btn">Run</button>
    </div>
    <div id="commands-div" style="display:none">
        <br />
        <h3>Commands</h3>
        <table class="table table-bordered table-condensed table-hover table-striped">
            <thead>
                <tr>
                    <th>Time</th>
                    <th style="width:60%">Command</th>
                    <th>Status</th>
                </tr>
            </thead>
            <tbody id="command-table"></tbody>
        </table>
    </div>
</div>
<br />


<div>
    <h2> Job analytics and monitoring:</h2>
    <iframe src="@ViewData["grafana"]/dashboard/db/job-status?var-job_name=@ViewData["jobid"]" width="100%" height="1000" frameborder="0"></iframe>
</div>
<style>

    .grey-box {
        background-color: #eee;
        white-space: pre;
        word-wrap: normal;
    }

    .scroll-box {
        overflow-x: scroll;
        overflow-y: scroll;
    }

    .grey-box, .cmd-textbox {
        font-family: 'Courier New';
        font-weight: 600;
        font-size: 12px;
    }

    .cmd-textbox {
        width: 100%;
        max-width: none;
    }

    #endpointsPanel {
        overflow-y: auto;
    }

    .alert {
        display: none;
        position: fixed;
        top: 50%;
        left: 50%;
        min-width: 300px;
        max-width: 600px;
        transform: translate(-50%,-50%);
        z-index: 99999;
        padding: 15px;
        border-radius: 3px;
    }

    .alert-message {
        background-color: #333; /* Black background color */
        color: #fff; /* White text color */
        text-align: center; /* Centered text */
        font-size: 15px;
    }

    code {
        color: #000000;
        background-color: inherit;
    }
</style>
